/*
组件说明：

  在屏幕中央弹出一个浮层，用于展示内容，比如提示信息、表单、选择框等。
  支持多种动画效果，包括淡入淡出（fade）、从底部滑入（slide-up）、从顶部滑入（slide-down）以及无动画（none）。
  带有半透明遮罩层，遮罩层可以配置是否允许点击关闭弹窗。
  支持安卓物理返回键拦截，方便控制弹窗关闭行为。
  通过 visible 属性控制弹窗显示与隐藏。
  通过动画控制弹窗的显示和隐藏过程，提升用户体验。
  支持自定义弹窗内容和样式。
*/
import React from 'react'
import {Animated, BackHandler, Dimensions, Easing, StyleProp, StyleSheet, TouchableWithoutFeedback, View, ViewStyle} from 'react-native'
import Portal from '../portal'

// 定义返回键回调类型，返回boolean表示是否拦截返回事件
export type CallbackOnBackHandler = () => boolean

// 样式定义
const styles = StyleSheet.create({
  wrap: {
    flex: 1,
    height: '100%',
    width: '100%',
    alignItems: 'center', // 水平居中
    justifyContent: 'center', // 垂直居中
  },
  mask: {
    backgroundColor: 'rgba(51,51,51,0.7)', // 半透明遮罩层颜色
  },
  content: {
    alignSelf: 'center',
    alignItems: 'center',
    justifyContent: 'center',
  },
  absolute: {
    position: 'absolute', // 绝对定位，覆盖全屏
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
  },
})

// 获取屏幕尺寸，用于动画计算
const screen = Dimensions.get('window')

// Modal组件的props类型定义
export interface IModalPropTypes {
  wrapStyle?: StyleProp<ViewStyle> // 外层容器样式
  maskStyle?: StyleProp<ViewStyle> // 遮罩层样式
  style?: {} // 内容区域样式
  animationType: 'none' | 'fade' | 'slide-up' | 'slide-down' // 动画类型
  animationDuration?: number // 动画时长，单位毫秒
  visible: boolean // 是否显示Modal
  maskClosable?: boolean // 点击遮罩是否关闭Modal
  onClose?: () => void // 关闭回调
  onAnimationEnd?: (visible: boolean) => void // 动画结束回调
  onRequestClose?: CallbackOnBackHandler // 安卓返回键回调
  children?: React.ReactNode // Modal内容
}

export default class RCModal extends React.Component<IModalPropTypes, any> {
  // 默认props
  static defaultProps = {
    wrapStyle: styles.wrap,
    maskStyle: styles.mask,
    animationType: 'slide-up',
    animationDuration: 300,
    visible: false,
    maskClosable: true,
    onClose() {
      // 默认空函数
    },
    onAnimationEnd() {
      // 默认空函数
    },
  } as IModalPropTypes

  animMask: any // 遮罩动画实例
  animDialog: any // 弹窗动画实例

  constructor(props: IModalPropTypes) {
    super(props)
    const {visible} = props
    this.state = {
      // 弹窗位置动画值，控制上下滑动
      position: new Animated.Value(this.getPosition(visible)),
      // 弹窗缩放动画值，控制fade动画时缩放
      scale: new Animated.Value(this.getScale(visible)),
      // 遮罩透明度动画值
      opacity: new Animated.Value(this.getOpacity(visible)),
      // 控制Modal是否渲染
      modalVisible: visible,
    }
  }

  componentDidMount() {
    // 监听安卓物理返回键
    BackHandler.addEventListener('hardwareBackPress', this.onBackAndroid)
  }

  // 旧版生命周期，接收新props时触发
  // 当visible变为true时，设置modalVisible为true，开始显示Modal
  // eslint-disable-next-line camelcase
  UNSAFE_componentWillReceiveProps(nextProps: IModalPropTypes) {
    if (this.shouldComponentUpdate(nextProps, null)) {
      this.setState({
        modalVisible: true,
      })
    }
  }

  // 控制组件是否重新渲染
  shouldComponentUpdate(nextProps: IModalPropTypes, nextState: any) {
    // visible为true或者visible发生变化时更新
    if (this.props.visible || this.props.visible !== nextProps.visible) {
      return true
    }
    // modalVisible状态变化时更新
    if (nextState) {
      if (nextState.modalVisible !== this.state.modalVisible) {
        return true
      }
    }
    return false
  }

  componentDidUpdate(prevProps: IModalPropTypes) {
    const {props} = this
    // visible属性变化时，触发动画
    if (prevProps.visible !== props.visible) {
      this.animateDialog(props.visible)
    }
  }

  componentWillUnmount() {
    // 移除安卓返回键监听，停止动画
    BackHandler.removeEventListener('hardwareBackPress', this.onBackAndroid)
    this.stopDialogAnim()
  }

  // 安卓物理返回键处理函数
  onBackAndroid = () => {
    const {onRequestClose} = this.props
    if (typeof onRequestClose === 'function') {
      // 调用外部传入的返回键回调，返回true阻止默认行为，false允许默认行为
      return onRequestClose()
    }
    // 默认不拦截返回键事件
    return false
  }

  // 遮罩层动画，控制透明度渐变
  animateMask = (visible: boolean) => {
    this.stopMaskAnim()
    // 先设置初始透明度为相反状态
    this.state.opacity.setValue(this.getOpacity(!visible))
    this.animMask = Animated.timing(this.state.opacity, {
      toValue: this.getOpacity(visible), // 目标透明度
      duration: this.props.animationDuration,
      useNativeDriver: true,
    })
    this.animMask.start(() => {
      this.animMask = null
    })
  }

  // 停止遮罩动画
  stopMaskAnim = () => {
    if (this.animMask) {
      this.animMask.stop()
      this.animMask = null
    }
  }

  // 停止弹窗动画
  stopDialogAnim = () => {
    if (this.animDialog) {
      this.animDialog.stop()
      this.animDialog = null
    }
  }

  // 弹窗动画控制，根据visible显示或隐藏弹窗
  animateDialog = (visible: boolean) => {
    this.stopDialogAnim()
    this.animateMask(visible) // 同步遮罩动画

    let {animationDuration} = this.props
    const {animationType} = this.props
    animationDuration = animationDuration!
    if (animationType !== 'none') {
      if (animationType === 'slide-up' || animationType === 'slide-down') {
        // 滑动动画，设置初始位置
        this.state.position.setValue(this.getPosition(!visible))
        this.animDialog = Animated.timing(this.state.position, {
          toValue: this.getPosition(visible), // 目标位置
          duration: animationDuration,
          easing: (visible ? Easing.elastic(0.8) : undefined) as any,
          useNativeDriver: true,
        })
      } else if (animationType === 'fade') {
        // 淡入淡出动画，结合缩放和透明度
        this.animDialog = Animated.parallel([
          Animated.timing(this.state.opacity, {
            toValue: this.getOpacity(visible),
            duration: animationDuration,
            easing: (visible ? Easing.elastic(0.8) : undefined) as any,
            useNativeDriver: true,
          }),
          Animated.spring(this.state.scale, {
            toValue: this.getScale(visible),
            useNativeDriver: true,
          }),
        ])
      }

      this.animDialog.start(() => {
        this.animDialog = null
        if (!visible) {
          // 隐藏时设置modalVisible为false，停止渲染
          this.setState({
            modalVisible: false,
          })
          // 移除安卓返回键监听
          BackHandler.removeEventListener('hardwareBackPress', this.onBackAndroid)
        }
        // 动画结束回调
        if (this.props.onAnimationEnd) {
          this.props.onAnimationEnd(visible)
        }
      })
    } else if (!visible) {
      // 无动画时直接隐藏
      this.setState({
        modalVisible: false,
      })
      BackHandler.removeEventListener('hardwareBackPress', this.onBackAndroid)
    }
  }

  // 外部调用关闭弹窗
  close = () => {
    this.animateDialog(false)
  }

  // 点击遮罩层关闭弹窗处理
  onMaskClose = () => {
    console.log('onCancel', this.props.maskClosable, this.props.onClose)
    if (this.props.maskClosable && this.props.onClose) {
      this.props.onClose()
      BackHandler.removeEventListener('hardwareBackPress', this.onBackAndroid)
    }
  }

  // 根据visible计算弹窗位置，配合滑动动画使用
  getPosition = (visible: boolean) => {
    if (visible) {
      return 0
    }
    // 隐藏时弹窗移出屏幕顶部或底部
    return this.props.animationType === 'slide-down' ? -screen.height : screen.height
  }

  // 计算缩放值，fade动画时使用
  getScale = (visible: boolean) => {
    return visible ? 1 : 1.05
  }

  // 计算遮罩透明度
  getOpacity = (visible: boolean) => {
    return visible ? 1 : 0
  }

  render() {
    const {props} = this
    // modalVisible为false时不渲染任何内容
    if (!this.state.modalVisible) {
      return null as any
    }
    // 根据动画类型设置对应的动画样式
    const animationStyleMap = {
      none: {},
      'slide-up': {transform: [{translateY: this.state.position}]},
      'slide-down': {transform: [{translateY: this.state.position}]},
      fade: {
        transform: [{scale: this.state.scale}],
        opacity: this.state.opacity,
      },
    }

    return (
      <Portal>
        <View style={[styles.wrap, props.wrapStyle]}>
          {/* 遮罩层，点击时触发关闭 */}
          <Animated.View style={[styles.absolute, {opacity: this.state.opacity}]}>
            <TouchableWithoutFeedback onPress={this.onMaskClose}>
              <View style={[styles.absolute, props.maskStyle]} />
            </TouchableWithoutFeedback>
          </Animated.View>

          {/* 弹窗内容区域，带动画 */}
          <Animated.View style={[styles.content, props.style, animationStyleMap[props.animationType]]}>{this.props.children}</Animated.View>
        </View>
      </Portal>
    )
  }
}
